package com.example.fast.web.exception;

import com.example.fast.common.exception.BaseException;
import com.example.fast.common.exception.TokenException;
import com.example.fast.common.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.stream.Collectors;

import static com.example.fast.common.exception.AppExceptionEnum.TOKEN_ERROR;
import static com.example.fast.common.exception.BaseErrorEnum.HTTP_REQUEST_FAILED;
import static com.example.fast.common.util.ExceptionUtils.exceptionSimpleDesc;
import static com.example.fast.common.util.ExceptionUtils.unwrapThrowable;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public R<Void> handleException(Exception e) {
        Throwable throwable = unwrapThrowable(e);
        log.error("未知异常: {}", throwable.toString(), throwable);
        return R.fail("未知异常, 请重试!");
    }

    @ExceptionHandler(BaseException.class)
    public R<Void> handleBaseException(BaseException e) {
        log.warn("未知自定义异常: {}", exceptionSimpleDesc(e));
        return R.of(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(TokenException.class)
    public R<Void> handleTokenException(TokenException e) {
        log.warn("token无效: {}", exceptionSimpleDesc(e));
        return R.fail(TOKEN_ERROR);
    }

    @ExceptionHandler(NullPointerException.class)
    public R<Void> handleNullPointerException(Exception e) {
        log.error("请求空指针异常: {}", exceptionSimpleDesc(e));
        return R.fail("请求记录不存在");
    }

    @ExceptionHandler({MissingServletRequestParameterException.class, ServletRequestBindingException.class})
    public R<Void> handleMvcException(Exception e) {
        log.error("请求参数不存在: {}", exceptionSimpleDesc(e));
        return R.fail(HTTP_REQUEST_FAILED, "请求参数不存在");
    }

    @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class})
    public R<Void> handleHttpMessageNotReadableException(Exception e) {
        log.warn("消息转换异常，请检查字段类型: {}", exceptionSimpleDesc(e));
        return R.fail(HTTP_REQUEST_FAILED, "请求异常，请检查输入参数");
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<Void> handleHttpRequestMethodNotSupportedException(Exception e) {
        log.warn("不支持的方法: {}", exceptionSimpleDesc(e));
        return R.fail(HTTP_REQUEST_FAILED, e.getMessage());
    }

    @ExceptionHandler({ConstraintViolationException.class, ValidationException.class, MethodArgumentNotValidException.class, BindException.class})
    public R<Void> handleValidateException(Exception e) {
        String errMsg = e.getMessage();
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException ex = (ConstraintViolationException) e;
            errMsg = ex.getConstraintViolations()
                       .stream()
                       .map(ConstraintViolation::getMessage)
                       .collect(Collectors.toList())
                       .toString();
        } else if (e instanceof MethodArgumentNotValidException) {
            errMsg = buildBindingResultMessage(((MethodArgumentNotValidException) e).getBindingResult());
        } else if (e instanceof BindException) {
            errMsg = buildBindingResultMessage(((BindException) e).getBindingResult());
        }

        log.warn("入参校验不通过: {}, {}", errMsg, exceptionSimpleDesc(e));
        return R.validate(errMsg);
    }

    private String buildBindingResultMessage(BindingResult bindingResult) {
        return bindingResult.getFieldErrors()
                            .stream()
                            .map(DefaultMessageSourceResolvable::getDefaultMessage)
                            .collect(Collectors.toList())
                            .toString();
    }
}
